/****************************************************************************
 * Copyright (C) 2025 Chenyz. All rights reserved.
 *
 * @file    my_muti_button.c
 *
 * @par     dependencies
 *          - my_muti_button.h
 *
 * @author  Chenyz
 *
 * @brief   LED driver hardware abstraction layer with following features:
 *          - Configurable blinking patterns and duty cycles
 *          - Cross-platform time management (bare-metal/RTOS)
 *          - Hardware-agnostic operation interface
 *
 * @version V1.0
 * @date    2025-05-11
 *
 * @note    Coding standard:
 *          - 1 tab == 4 spaces
 *          - Follow ISO C99 standard
 *
 ***************************************************************************/

#include "my_multi_button.h"

ButtonType *ButtonListHead = NULL;
#define CALLBACK_EVENT()                                                       \
    do                                                                         \
    {                                                                          \
        ButtonInstance->ButtonCallBacksArray[ButtonInstance->ButtonEvent]();   \
    } while(0);

void Button_Init(ButtonType *ButtonInstance,
                 uint8_t     (*pfButtonGetLevel)(uint8_t ButtonID),
                 uint8_t     ButtonActiveLevel,
                 uint8_t     ButtonID)
{
    memset((ButtonType *) ButtonInstance, 0, sizeof(ButtonType));
    ButtonInstance->pfButtonGetLevel   = pfButtonGetLevel;
    ButtonInstance->ButtonCurrentLevel = BUTTON_HIGH_LEVEL;
    ButtonInstance->ButtonActiveLevel  = BUTTON_LOW_LEVEL;
    ButtonInstance->ButtonID           = ButtonID;
}

void Button_Process(ButtonType *ButtonInstance)
{
    uint8_t ButtonLevel =
        ButtonInstance->pfButtonGetLevel(ButtonInstance->ButtonID);

    if(ButtonInstance->ButtonStatus > BUTTON_IDLE_STATUS)
    {
        ButtonInstance->ButtonTicks++;
    }
    /* Debounce	*/
    if(ButtonLevel != ButtonInstance->ButtonCurrentLevel)
    {
        ButtonInstance->ButtonDebounceCount++;
        if(ButtonInstance->ButtonDebounceCount > DEBOUNCE_TICKS)
        {
            /* Debounce finished,read the button level into current level*/
            ButtonInstance->ButtonCurrentLevel  = ButtonLevel;
            ButtonInstance->ButtonDebounceCount = 0;
        }
    }
    else
    {
        ButtonInstance->ButtonDebounceCount = 0;
    }

    /*
        State Machine
    */
    switch(ButtonInstance->ButtonStatus)
    {
        case BUTTON_IDLE_STATUS:
            if(ButtonInstance->ButtonCurrentLevel ==
               ButtonInstance->ButtonActiveLevel)
            /*  Pressed down    */
            {
                ButtonInstance->ButtonEvent  = BUTTON_PRESS_DOWN;
                ButtonInstance->ButtonStatus = BUTTON_PRESS_STATUS;
                ButtonInstance->ButtonTicks  = 0;
                ButtonInstance->ButtonRepeat = 1;
                /* Call backs here */
                CALLBACK_EVENT();
                // printf("BUTTON_PRESS_DOWN \r\n");
            }
            break;
        case BUTTON_PRESS_STATUS:
            if((ButtonInstance->ButtonCurrentLevel !=
                ButtonInstance->ButtonActiveLevel))
            /*   Pressed up in long press ticks     */
            {
                ButtonInstance->ButtonEvent  = BUTTON_PRESS_UP;
                ButtonInstance->ButtonStatus = BUTTON_RELEASE_STATUS;
                ButtonInstance->ButtonTicks  = 0;
                /* Call backs here */
                CALLBACK_EVENT();
                // printf("BUTTON_PRESS_UP \r\n");
            }
            else if(ButtonInstance->ButtonTicks > LONG_PRESS_TICKS)
            {
                /*   not pressed up and time over long press ticks     */
                ButtonInstance->ButtonEvent  = BUTTON_LONG_PRESS_START;
                ButtonInstance->ButtonStatus = BUTTON_HOLDING_STATUS;
                /* Call backs here */
                CALLBACK_EVENT();
                // printf("BUTTON_LONG_PRESS_START \r\n");
            }
            break;
        case BUTTON_RELEASE_STATUS:
            if((ButtonInstance->ButtonCurrentLevel ==
                ButtonInstance->ButtonActiveLevel) &&
               (ButtonInstance->ButtonTicks < SHORT_PRESS_TICKS))
            /*   Pressed down again in short time    */
            {
                ButtonInstance->ButtonEvent = BUTTON_PRESS_DOWN;
                /* Call backs here */
                CALLBACK_EVENT();
                // printf("BUTTON_PRESS_DOWN \r\n");
                ButtonInstance->ButtonRepeat++;
                if(2 == ButtonInstance->ButtonRepeat)
                {
                    ButtonInstance->ButtonStatus = BUTTON_DOUBLE_CLICK_STATUS;
                    ButtonInstance->ButtonEvent  = BUTTON_DOUBLE_CLICK;
                    ButtonInstance->ButtonTicks  = 0;
                    /* Call backs here */
                    CALLBACK_EVENT();
                    // printf("BUTTON_DOUBLE_CLICK \r\n");
                }
                else
                {
                    /* ButtonRepeat > 2 do nothing  */
                }
            }
            else if(ButtonInstance->ButtonTicks > SHORT_PRESS_TICKS)
            {
                ButtonInstance->ButtonStatus = BUTTON_IDLE_STATUS;
                ButtonInstance->ButtonTicks  = 0;
                ButtonInstance->ButtonRepeat = 0;
            }
            break;
        case BUTTON_DOUBLE_CLICK_STATUS:
            if((ButtonInstance->ButtonCurrentLevel !=
                ButtonInstance->ButtonActiveLevel) &&
               (ButtonInstance->ButtonTicks < LONG_PRESS_TICKS))
            /*   Pressed up in long press ticks     */
            {
                /* Call backs here */
                ButtonInstance->ButtonStatus =
                    BUTTON_DOUBLE_CLICKRELEASE_STATUS;
                ButtonInstance->ButtonEvent = BUTTON_PRESS_UP;
                ButtonInstance->ButtonTicks = 0;
                /* Call backs here */
                CALLBACK_EVENT();
                // printf("BUTTON_PRESS_UP \r\n");
            }
            else
            {
                if(ButtonInstance->ButtonTicks > LONG_PRESS_TICKS)
                {
                    /*   not pressed up and time over long press ticks     */
                    ButtonInstance->ButtonEvent  = BUTTON_LONG_PRESS_START;
                    ButtonInstance->ButtonStatus = BUTTON_HOLDING_STATUS;
                    /* Call backs here */
                    CALLBACK_EVENT();
                    // printf("BUTTON_LONG_PRESS_START \r\n");
                }
            }
            break;
        case BUTTON_DOUBLE_CLICKRELEASE_STATUS:
            if((ButtonInstance->ButtonCurrentLevel ==
                ButtonInstance->ButtonActiveLevel) &&
               (ButtonInstance->ButtonTicks < SHORT_PRESS_TICKS))
            /*   Pressed down again in short time    */
            {
                ButtonInstance->ButtonEvent = BUTTON_PRESS_DOWN;
                /* Call backs here */
                CALLBACK_EVENT();
                // printf("BUTTON_PRESS_DOWN \r\n");
                ButtonInstance->ButtonRepeat++;
                if(3 == ButtonInstance->ButtonRepeat)
                {
                    ButtonInstance->ButtonStatus = BUTTON_TRIPLE_CLICK_STATUS;
                    ButtonInstance->ButtonEvent  = BUTTON_TRIPLE_CLICK;
                    ButtonInstance->ButtonTicks  = 0;
                    /* Call backs here */
                    CALLBACK_EVENT();
                    // printf("BUTTON_TRIPLE_CLICK \r\n");
                }
                else
                {
                    /* ButtonRepeat > 2 do nothing  */
                }
            }
            else if(ButtonInstance->ButtonTicks > SHORT_PRESS_TICKS)
            {
                ButtonInstance->ButtonStatus = BUTTON_IDLE_STATUS;
                ButtonInstance->ButtonTicks  = 0;
                ButtonInstance->ButtonRepeat = 0;
            }
            break;
        case BUTTON_TRIPLE_CLICK_STATUS:
            if((ButtonInstance->ButtonCurrentLevel !=
                ButtonInstance->ButtonActiveLevel) &&
               (ButtonInstance->ButtonTicks < LONG_PRESS_TICKS))
            /*   Pressed up in long press ticks     */
            {
                ButtonInstance->ButtonEvent  = BUTTON_PRESS_UP;
                ButtonInstance->ButtonStatus = BUTTON_IDLE_STATUS;
                ButtonInstance->ButtonTicks  = 0;
                /* Call backs here */
                CALLBACK_EVENT();
                // printf("BUTTON_PRESS_UP \r\n");
            }
            else
            {
                if(ButtonInstance->ButtonTicks > LONG_PRESS_TICKS)
                {
                    /*   not pressed up and time over long press ticks     */
                    ButtonInstance->ButtonEvent  = BUTTON_LONG_PRESS_START;
                    ButtonInstance->ButtonStatus = BUTTON_HOLDING_STATUS;
                    /* Call backs here */
                    CALLBACK_EVENT();
                    // printf("BUTTON_LONG_PRESS_START \r\n");
                }
            }
            break;
        case BUTTON_HOLDING_STATUS:
            if(ButtonInstance->ButtonCurrentLevel !=
               ButtonInstance->ButtonActiveLevel)
            {
                /*   Pressed up */
                ButtonInstance->ButtonEvent  = BUTTON_PRESS_UP;
                ButtonInstance->ButtonStatus = BUTTON_IDLE_STATUS;
                ButtonInstance->ButtonTicks  = 0;
                /* Call backs here */
                CALLBACK_EVENT();
                // printf("BUTTON_PRESS_UP \r\n");
            }
            else
            {
                /*   Holding */
                ButtonInstance->ButtonEvent = BUTTON_LONG_PRESS_HOLD;
                /* Call backs here */
                CALLBACK_EVENT();
                // printf("BUTTON_LONG_PRESS_HOLD \r\n");
            }
            break;
        default:
            ButtonInstance->ButtonStatus = BUTTON_IDLE_STATUS;
            break;
    }
    return;
}

bool Button_Register(ButtonType *ButtonInstance)
{
    bool        breturn;
    ButtonType *current;
    if(NULL == ButtonInstance)
    {
        breturn = false;
        goto EXIT;
    }
    if(NULL == ButtonListHead)
    {
        ButtonListHead = ButtonInstance;
    }
    else
    {
        current = ButtonListHead;
        while(NULL != (current->ButtonNext))
        {
            current = current->ButtonNext;
        }
        current->ButtonNext = ButtonInstance;
    }
    breturn = true;
EXIT:
    return breturn;
}

bool Button_UnRegister(uint8_t ButtonID)
{
    ButtonType *current = ButtonListHead;
    ButtonType *prev    = NULL;
    while(current != NULL)
    {
        if(current->ButtonID == ButtonID)
        {
            if(prev == NULL)
            {
                ButtonListHead = current->ButtonNext;
            }
            else
            {
                prev->ButtonNext = current->ButtonNext;
            }
            return true;
        }
        prev    = current;
        current = current->ButtonNext;
    }
    return false;
}

uint8_t Button_Get_Event(ButtonType *ButtonInstance)
{
    return ButtonInstance->ButtonEvent;
}

void Button_List_Porcess(void)
{
    ButtonType *pButtonListHead = ButtonListHead;
    while(NULL != pButtonListHead)
    {
        Button_Process(pButtonListHead);
        pButtonListHead = pButtonListHead->ButtonNext;
    }
}

bool Button_Callbacks_Attach(ButtonType         *ButtonInstance,
                             uint8_t             ButtonEvent,
                             ButtonCallBacksType Callback)
{
    bool breturn;
    if(NULL == ButtonInstance || NULL == Callback)
    {
        breturn = false;
        goto EXIT;
    }
    ButtonInstance->ButtonCallBacksArray[ButtonEvent] = Callback;
    breturn                                           = true;
EXIT:
    return breturn;
}
